# 「创建应用」
from flask import Flask
app = Flask(__name__)
    
# 「建数据库」
import os
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin

basedir = os.path.abspath(app.root_path)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.db')

# import pymysql
# Windows MySQL
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:123456@192.168.1.14:3306/mydata?charset=utf8'

# centOS MariaDB
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:123456@192.168.1.102:3306/mydata?charset=utf8'

# UOS MySQL
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:12345678@192.168.1.102:3306/mydata?charset=utf8'


app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
class User(db.Model, UserMixin): #2.0修改
    id = db.Column(db.Integer, primary_key = True)
    email = db.Column(db.String) #邮箱
    isadmin = db.Column(db.Boolean, default = False) #是管理员
    password = db.Column(db.String) #密码
    name = db.Column(db.String) #姓名
    gender = db.Column(db.Boolean) #性别
    birthday = db.Column(db.Date) #出生日期
    education = db.Column(db.String(1)) #教育程度
    image = db.Column(db.String) #照片 
    hobby = db.Column(db.String(4)) #爱好
    skill = db.Column(db.Text) #特长
    verify = db.Column(db.Boolean) #审核

# 「用户注册」
from flask_bootstrap import Bootstrap
app.config['SECRET_KEY'] = 'fahsdjfahdksjfkjdsjhf' #设汉字remember报错
bootstrap = Bootstrap(app) 

from flask_wtf import FlaskForm
from wtforms import \
    StringField, \
    PasswordField, \
    SubmitField, \
    RadioField, \
    BooleanField, \
    DateField
from wtforms.validators import \
    DataRequired, \
    Length, \
    EqualTo, \
    Email # 输入验证所需

class RegistForm1(FlaskForm):
    email = StringField('邮箱：', 
        validators = [
            DataRequired(),
            Email('邮箱格式错误')], 
        render_kw = {'placeholder': u'输入邮箱地址（登录用）'})
    
    password = PasswordField('密码：', 
        validators = [
            DataRequired(), 
            Length(6, 18)], 
        render_kw={'placeholder': u'输入密码（6-18位）'})
    
    confirm = PasswordField('确认密码：', 
        validators = [
            DataRequired(), 
            Length(6, 18), 
            EqualTo('password', "密码不一致")], 
        render_kw = {'placeholder': u'再次输入密码（两次密码必须一致）'})
    
    name = StringField('姓名：', 
        validators = [DataRequired()], 
        render_kw = {'placeholder': u'请输入姓名'})
    
    gender = RadioField('性别：', 
        choices = [
            (1,'男'),
            (0,'女')], 
        validators = [DataRequired()])
    
    birthday = DateField('出生日期：')

    submit = SubmitField('下一步', 
        render_kw = {"style" : "background:#007bff; color : white;"}) 

from wtforms import \
    SelectField, \
    SelectMultipleField, \
    TextAreaField

# 上传文件所需
from flask_wtf.file import \
    FileRequired, \
    FileAllowed, \
    FileField

class RegistForm2(FlaskForm):
    education = SelectField('文化程度：',
        choices = 
            [
                ('1', '1-专科'),
                ('2', '2-本科'), 
                ('3', '3-硕士'), 
                ('4', '4-博士')
            ], 
        validators = [DataRequired()])
    
    image = FileField('照片：',
        validators = [FileRequired(), 
            FileAllowed(['jpeg', 'jpg', 'png', 'gif'],'只能上传图片')],
            render_kw = {"style" : "width:100%; border: 1px solid; \
                border-color:silver; padding:4px;border-radius:4px;"})

    
    hobby = SelectMultipleField('爱好：', 
        choices = 
            (
                ('1', '1-篮球'), 
                ('2', '2-足球'), 
                ('3', '3-健身'), 
                ('4', '4-其他')
            ))

    skill = TextAreaField('特长：', 
        validators = [DataRequired()], 
        render_kw={'placeholder': u'请输入个人特长', 'rows': 3}) #3行  

    submit = SubmitField('注册', 
            render_kw = {"style" : "background:#28a745; color : white;"}) # 提交 

from flask import render_template, flash, redirect, url_for, session
from werkzeug.security import generate_password_hash #hash加密

@app.route('/regist1',methods = ['GET','POST'])
def regist1():
    form = RegistForm1()
    if form.validate_on_submit(): #若内容有效
        email = form.email.data
        password = generate_password_hash(form.password.data)
        name = form.name.data

        # python化
        # if form.gender.data == '1': gender = True
        # else: gender = False
        gender = True if form.gender.data == '1' else False       
        # 

        birthday = form.birthday.data

        session['email'] = email
        session['password'] = password
        session['name'] = name
        session['gender'] = gender
        session['birthday'] = birthday.strftime("%Y-%m-%d") 

        return redirect(url_for('regist2'))
    if form.errors: flash(form.errors,'danger')
    return render_template("regist.html", form = form) 

from datetime import datetime

@app.route('/regist2',methods = ['GET','POST'])
def regist2():
    form = RegistForm2()
    if form.validate_on_submit(): 
        email = session.get('email')
        password = session.get('password')
        name = session.get('name')
        gender = session.get('gender')
        birthday = \
            datetime.strptime(session.get('birthday') , '%Y-%m-%d')

        user = User.query.filter_by(email = email).first()
        if user: 
            flash("用户已注册",'warning')
            return render_template("regist.html", form = form)
        else: 
            education = form.education.data
            ext = form.image.data.filename.split('.')[1]
            strnow = datetime.now().strftime('%Y%m%d%H%M%s')
            prefix = '%s%s' % (strnow, str(random.randrange(1000, 10000)))

            imgname = f'{prefix}.{ext}' 
            form.image.data.save('static/image/' + imgname)
            # 多选框
            # python化
            # hobby=''
            # for h in form.hobby.data: hobby += h
            hobby = ''.join(form.hobby.data)
            # 

            skill = form.skill.data
            new = User(
                email = email, 
                password = password, 
                name = name,
                gender = gender, 
                birthday = birthday,
                education = education, 
                image = imgname,
                hobby = hobby,
                skill = skill)
            db.session.add(new) 
            db.session.commit() #提交
            flash('注册成功','success')
            return redirect(url_for('login'))
    if form.errors: flash(form.errors,'danger')
    return render_template("regist.html", form = form) 

# 「用户登录」
from flask_login import LoginManager

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login' #指定登录视图
login_manager.login_message_category = 'info' #提示信息类型
login_manager.login_message = u'请先登录！' #提示信息

#【登录表单】
from wtforms.validators import ValidationError
class LoginForm(FlaskForm):
    email = StringField('邮箱：', 
        validators = [
            DataRequired(),
            Email('邮箱格式错误')], 
        render_kw = {'placeholder': u'输入邮箱地址（登录用）'})
    password = PasswordField('密码：', 
        validators = [DataRequired(), Length(6,18)], 
        render_kw = {'placeholder': u'输入用户密码（6-18位）'})
    # 验证码输入框
    code = StringField('验证码：', 
        validators = [DataRequired()], 
        render_kw = {'placeholder': 
            u'输入验证码（不分大小写），单击可刷新'})
    # 验证函数
    def validate_code(self, data):
        input_code = data.data
        code = session.get('valid')
        if input_code.lower() != code.lower(): # 判断输入的验证码
            raise ValidationError('验证码错误')        
    
    remember = BooleanField('记住我')
    submit = SubmitField('登录')

# 生成随数字
import random

def get_random_color():
    return random.randint(0, 255), \
        random.randint(0, 255), random.randint(0, 255)

# 生成图片
from PIL import Image, ImageFont, ImageDraw, ImageFilter

def generate_image(length):
    code_ku = 'abcdefg1234567890ABCDEFG'
    image = Image.new('RGB', (120,60), color = get_random_color())
    font = ImageFont.truetype("./font/NotoSans-Bold.ttf", size = 35)
    draw = ImageDraw.Draw(image)
    code = ''
    for i in range(length):
        c = random.choice(code_ku)
        code += c
        draw.text((5 + random.randint(4,7) + 25*i, random.randint(4, 7)), 
            text = c, fill=get_random_color(), font = font)
    image = image.filter(ImageFilter.EDGE_ENHANCE)
    return image, code

# 验证图片
from flask import make_response
from io import BytesIO

@app.route('/image')
def get_image():
    image, code = generate_image(4)
    buffer = BytesIO()
    image.save(buffer, 'jpeg')
    buf_bytes = buffer.getvalue()
    session['valid'] = code
    response = make_response(buf_bytes)
    response.headers['Content-Type'] = 'image/jpeg'
    return response

#当调用current_user时，自动调用该函数，返回用户信息
@login_manager.user_loader
def load_user(user_id):
    user = User.query.get(int(user_id))
    return user

#【登录视图】
from flask_login import login_user, current_user
from werkzeug.security import check_password_hash

@app.route('/',methods=['GET','POST'])
@app.route('/login',methods=['GET','POST'])
def login():
    #若已登录，直接进入用户主界面
    if current_user.is_authenticated:
        return redirect(url_for('logined'))   

    form = LoginForm()
    if form.validate_on_submit(): 
        email = form.email.data 
        password = form.password.data 
        remember = form.remember.data
        user = User.query.filter_by(email = email).first()
        if user:
            if check_password_hash(user.password,password): 
                if user.verify: #若审核通过
                    login_user(user, remember)
                    return redirect(url_for('logined'))
                else: flash('审核不通过或还未审核！','warning')
            else: flash('密码错误！','warning')
        else: 
            flash('用户不存在，请注册！','warning')
            return redirect(url_for('regist1'))   
    if form.errors: flash(form.errors,'danger')
    return render_template("login.html", form = form)


# 「用户主页」
from flask_ckeditor import CKEditor, CKEditorField

# 【参数配置】
ckeditor = CKEditor(app)
app.config['CKEDITOR_SERVE_LOCAL'] = True
app.config['CKEDITOR_ENABLE_CSRF'] = True

from wtforms.validators import NumberRange
from wtforms import FloatField


#【主界面表单】
class LoginedForm(FlaskForm):
    course1 = StringField('科目：', 
        validators = [DataRequired()], 
        render_kw = {'placeholder': u'输入科目名称'})
    course2 = StringField('科目：', 
        validators = [DataRequired()], 
        render_kw = {'placeholder': u'输入科目名称'})
    
    score1 = FloatField('成绩：', 
        validators = [DataRequired(), NumberRange(min=0, max=100)], #只允许0~100间的数字
        render_kw = {'placeholder': u'成绩（0~100）'})
    score2 = FloatField('成绩：', 
        validators = [DataRequired(), NumberRange(min=0, max=100)], 
        render_kw = {'placeholder': u'成绩（0~100）'})
    
    body = CKEditorField('输入简历：')
    submit = SubmitField('开始')

def addone():
    #统计访问量
    # python化
    with open("visit.json", 'r') as f: var = json.load(f)
    visit = var['visit'] + 1
    var = {"visit": visit}
    # python化
    with open("visit.json", 'w') as f: json.dump(var, f)
    return visit

def name_card(docxout, user):
    #添加1级标题
    Head = docxout.add_heading('', level=1)
    run = Head.add_run("名片")
    #设置字体颜色为白色
    run.font.color.rgb = RGBColor(255, 255, 255)
    #设置高亮背景颜色
    run.font.highlight_color = MSO_THEME_COLOR_INDEX.BACKGROUND_1  
    #添加基本信息
    par = docxout.add_paragraph('', style='List Bullet')
    run = par.add_run("姓名：")
    run.bold = True
    run.font.color.rgb = RGBColor(0xff, 0x00 , 0x00)
    run = par.add_run(user.name)

    # python化
    # if user.gender: gender = '男'
    # else: gender = '女'
    gender = '男' if user.gender else '女'
    # 

    par = docxout.add_paragraph('', style='List Bullet')
    run = par.add_run("性别：")
    run.bold = True
    run.font.color.rgb = RGBColor(0xff, 0x00 , 0x00)
    run = par.add_run(gender)

    par = docxout.add_paragraph('', style='List Bullet')
    run = par.add_run("生日：")
    run.bold = True
    run.font.color.rgb = RGBColor(0xff, 0x00 , 0x00)
    run = par.add_run(str(user.birthday.year)+'年')
    run = par.add_run(str(user.birthday.month)+'月')
    run = par.add_run(str(user.birthday.day)+'日')

    if user.education == '1': education = '高职专科'
    elif user.education == '2': education = '大学本科'
    elif user.education == '3': education = '硕士研究生'
    elif user.education == '4': education = '博士研究生'

    par = docxout.add_paragraph('', style='List Bullet')
    run = par.add_run("文化程度：")
    run.bold = True
    run.font.color.rgb = RGBColor(0xff, 0x00 , 0x00)
    run = par.add_run(education)

    par = docxout.add_paragraph('', style='List Bullet')
    run = par.add_run("邮箱：")
    run.bold = True
    run.font.color.rgb = RGBColor(0xff, 0x00 , 0x00)
    run = par.add_run(user.email)

    hobby = ''
    # python化
    if '1' in user.hobby: hobby += '篮球'
    if '2' in user.hobby: hobby += '足球'
    if '3' in user.hobby: hobby += '健身'
    if '4' in user.hobby: hobby += '其他'

    par = docxout.add_paragraph('', style='List Bullet')
    run = par.add_run("爱好：")
    run.bold = True
    run.font.color.rgb = RGBColor(0xff, 0x00 , 0x00)
    run = par.add_run(hobby)

def course_table(docxout,form):
    #开始
    # python化
    course1 = form.course1.data; course2 = form.course2.data
    score1 = form.score1.data; score2 = form.score2.data

    Head = docxout.add_heading('', level=1)
    run = Head.add_run("成绩")
    run.font.color.rgb = RGBColor(255, 255, 255)
    run.font.highlight_color = MSO_THEME_COLOR_INDEX.BACKGROUND_1
    paraout = docxout.add_paragraph('', style = 'Body Text')
    paraout.alignment = WD_ALIGN_PARAGRAPH.CENTER
    run = paraout.add_run('精品课程成绩单')
    run.bold = True
    run.font.color.rgb = RGBColor(0xff, 0x00 , 0x00)
    
    #创建表格（1行4列）
    table = docxout.add_table(rows = 1, cols = 2)
    # 设定表格格式
    table.style = 'Table Grid'

    #添加表格表头
    hdr_cells = table.rows[0].cells
    hdr_cells[0].paragraphs[0].add_run('科目').bold = True
    hdr_cells[1].paragraphs[0].add_run('成绩').bold = True
    #表头内容居中
    hdr_cells[0].paragraphs[0].alignment = \
        WD_PARAGRAPH_ALIGNMENT.CENTER
    hdr_cells[1].paragraphs[0].alignment = \
        WD_PARAGRAPH_ALIGNMENT.CENTER
    #添加行内容
    row_cells = table.add_row().cells
    row_cells[0].text = course1
    row_cells[1].text = str(score1)
    #添加行内容
    row_cells = table.add_row().cells
    row_cells[0].text = course2
    row_cells[1].text = str(score2)

def wordcloud2docx(docxout, result):
    Head = docxout.add_heading('', level=1)
    run = Head.add_run("画像")
    run.font.color.rgb = RGBColor(255, 255, 255)
    run.font.highlight_color = MSO_THEME_COLOR_INDEX.BACKGROUND_1 
    # 设定图片字体
    mpl.rcParams['font.sans-serif'] = ['SimHei']
    mpl.rcParams['axes.unicode_minus'] = False
    #设定词云图参数
    wc = WordCloud(
        font_path = './usr/share/fonts/wps-office-fonts/FZSSK.TTF',
        background_color = 'white',   #背景颜色
        max_font_size = 166,          #字体大小166，6
        min_font_size = 6,
        #背景图片，根据其大小确定词云图大小
        mask = plt.imread('static/sys_Heart.jpg'),  
        max_words = len(result)
    )
    #创建词云图
    wc.generate(result)
    wc.to_file('static/img.png')    #图片保存
    #为了PDF文件显示效果，留一个空行
    paraout = docxout.add_paragraph('', style = 'Body Text')
    pic = docxout.add_picture('static/img.png')
    pic.width = int(pic.width * 0.70)  # 宽度缩放为原来的70%
    pic.height = int(pic.height * 0.70)  # 高度缩放为原来的70%
    # 居中对齐，paragraphs[-1]获取最后一个段落，
    # 即刚添加的图片所在段落
    docxout.paragraphs[-1].alignment = WD_PARAGRAPH_ALIGNMENT.CENTER 

from docx import Document
from docx.shared import RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_PARAGRAPH_ALIGNMENT
from docx.enum.dml import MSO_THEME_COLOR_INDEX
from docx.shared import Pt, Inches
# from docx.oxml.ns import qn
import json
import jieba
from pypinyin import lazy_pinyin, Style
from wordcloud import WordCloud
import matplotlib.pyplot as plt
from pylab import mpl
from flask_login import logout_user, login_required

import html
import re

#【主界面视图】
@app.route('/logined', methods=['GET','POST'])
@login_required
def logined():
    user = current_user
    visit = addone()
    form = LoginedForm()
    if form.validate_on_submit():
        #输出文档对象
        docxout = Document()

        #将用户照片添加到Word文档
        pic = docxout.add_picture(f'static/image/{user.image}')
        pic.width = Inches(1.2) #设定照片宽度
        pic.height = Inches(1.6) #设定照片高度
        # paragraphs[-1]获取最后一个段落，即刚添加的图片所在段落
        docxout.paragraphs[-1].alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT  

        #添加标题
        docxout.add_heading('个人简历', 0)
        name_card(docxout, user)

        Head = docxout.add_heading('', level=1)
        run = Head.add_run("特长")
        #设置字体颜色为白色
        run.font.color.rgb = RGBColor(255, 255, 255)
        #设置高亮背景颜色
        run.font.highlight_color = MSO_THEME_COLOR_INDEX.BACKGROUND_1  
        docxout.add_paragraph(user.skill, style = 'Intense Quote')

        course_table(docxout, form)
        
        Head = docxout.add_heading('', level=1)
        run = Head.add_run("简历")
        run.font.color.rgb = RGBColor(255, 255, 255)
        run.font.highlight_color = MSO_THEME_COLOR_INDEX.BACKGROUND_1 
        
        # # 消除html标签
        pattern = re.compile(r'<[^>]+>',re.S)
        body = pattern.sub('', html.unescape(form.body.data))

        paraout = docxout.add_paragraph(body, style = 'Body Text') #段落
        
        words = jieba.cut(body, cut_all = False) #分词
        result = " ".join(words)
        wordcloud2docx(docxout, result)

        #输出Docx文件名
        strnow = datetime.now().strftime('%Y%m%d%H%M%s')
        prefix = '%s%s' % (strnow, str(random.randrange(1000, 10000)))

        fdocx = f'static/file/{prefix}.docx'
        docxout.save(fdocx)

        flash('已完成，请下载', 'success')
        return render_template('logined.html', 
            form = form, 
            fdocx=fdocx, #Docx文档存放地址
            user = user, #用户信息
            visit=visit, #访问数量
            complete=True) #python代码执行完毕
    if form.errors: flash(form.errors, 'danger')
    return render_template('logined.html', 
        form=form, 
        user = user, 
        visit=visit)

# 【退出视图】
@app.route('/logout')
@login_required
def logout():
    logout_user()
    flash('您已安全退出', 'success') 
    return redirect(url_for('login'))

# 「密码修改」
class ChangeForm(FlaskForm):
    email = StringField('邮箱：', 
        validators = [DataRequired(),Email('邮箱格式错误')], 
        render_kw = {'placeholder': u'输入邮箱地址（登录用）'})
    password = PasswordField('原密码：', 
        validators = [DataRequired(), Length(6,18)], 
        render_kw = {'placeholder': u'输入原密码'})
    newpassword = PasswordField('新密码：', 
        validators = [DataRequired(), Length(6,18)], 
        render_kw = {'placeholder': u'输入新密码（6-18位）'})
    confirmpwd = PasswordField('确认新密码：', 
        validators = [DataRequired(), Length(6,18), 
            EqualTo('newpassword', message = "密码不一致")], 
        render_kw = {'placeholder': u'再次输入新密码（两次密码需一致）'})
    submit = SubmitField('修改',
        render_kw = {"style" : "background:#28a745; color : white;"})

@app.route('/change',methods=['GET','POST'])
# @login_required
def change():
    form = ChangeForm()
    if form.validate_on_submit():
        email = form.email.data 
        password = form.password.data 
        newpassword = form.newpassword.data 
        user = User.query.filter_by(email = email).first()
        if user:
            if check_password_hash(user.password,password):
                newpassword = generate_password_hash(form.newpassword.data)
                User.query.filter_by(id = user.id).update({'password': newpassword})
                db.session.commit()
                flash("密码修改成功", 'success')
                return redirect(url_for('login'))
            else:
                flash("原密码有误", 'warning')
                return render_template("change.html", form = form)
        else:
            flash("用户名有误", 'warning')
            return render_template("change.html", form = form)
    if form.errors: flash(form.errors,'danger')
    return render_template("change.html", form = form)


# 「邮件发送」

# 配置邮箱服务器
app.config['MAIL_SERVER'] = 'smtp.163.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_DEBUG'] = True
# 邮箱用户
app.config['MAIL_USERNAME'] = 'muhtar_xjedu@163.com'
# 用户授权码
app.config['MAIL_PASSWORD'] = 'LBITTDDCHDDQDKKW'

#创建Mail对象
from flask_mail import Mail
mail = Mail(app)

#【邮件表单】
class EmailForm(FlaskForm):
    email = StringField('邮箱：', 
        validators = [DataRequired(),Email('邮箱格式错误')], 
        render_kw = {'placeholder': u'输入邮箱地址（登录用）'})
    # 验证码输入框
    code = StringField('验证码：', 
        validators = [DataRequired()], 
        render_kw = {'placeholder': 
            u'输入验证码（不分大小写）'})
    # 验证函数
    def validate_code(self, data):
        input_code = data.data
        code = session.get('valid')
        if input_code.lower() != code.lower(): # 判断输入的验证码
            raise ValidationError('验证码错误')        
    submit = SubmitField('发送邮件',
        render_kw = {"style" : "background:#28a745; color : white;"})

#【邮件视图】
from flask_mail import Message
@app.route("/email", methods=['GET', 'POST'])
def email():
    form = EmailForm()
    if form.validate_on_submit():
        email = form.email.data 
        user = User.query.filter_by(email = email).first()
        if user:
            password = generate_password_hash('123456')
            user.password = password
            db.session.commit()

            # sender是发邮件地址，recipients是接收邮件地址
            msg = Message('初始密码', 
                sender='muhtar_xjedu@163.com', 
                recipients=[email])
            msg.body = "您的初始密码为：123456，请尽快修改！"
            mail.send(msg)
            flash('邮件已发送','success')
            return redirect(url_for('change')) 
        else: 
            flash('用户不存在','warning')
            return redirect(url_for('regist1'))
    if form.errors: flash(form.errors,'danger') 
    return render_template("email.html", form=form)  


# 「管理功能」

class AdminForm(FlaskForm):
    email = StringField('邮箱：', 
        validators = [DataRequired(),Email('邮箱格式错误')], 
        render_kw = {'placeholder': u'输入邮箱地址（登录用）'})
    password = PasswordField('密码：', 
        validators = [DataRequired(), Length(6,18)], 
        render_kw = {'placeholder': u'输入用户密码（6-18位）'})
    flaskadmin = BooleanField('登录Flask-Admin后台')
    submit = SubmitField('登录')


# 超级权限，管理管理员
app.config['SUPER_NAME'] = 'super@super.com'

from flask import request
@app.route('/admin',methods=['GET','POST'])
def admin():
    # python化
    # with open("admips.txt") as file: 
    #     admips = file.readlines()
    admips = [line for line in open("admips.txt")]
    # 

    ip = request.remote_addr + '\n'
    if ip not in admips:
        flash('本机不允许登录管理后台','danger')
        return redirect(url_for('login'))

    form = AdminForm()
    if form.validate_on_submit():
        email = form.email.data 
        password = form.password.data
        # python化
        with open("super.json", 'r') as f: var = json.load(f)
        supwd = var['password']
        #end

        
        sup = app.config['SUPER_NAME']
        if email == sup and password == supwd: 
            session['super'] = sup
            # return redirect(url_for('super'))
            user = User.query.first()
            return redirect(url_for('super', userid = user.id))

        
        flaskadmin = form.flaskadmin.data

        user = User.query.filter_by(email = email, isadmin = True).first()
        # python化
        if user:
            if check_password_hash(user.password,password): 
                if user.verify: 
                    login_user(user, False) 
                    if flaskadmin: return redirect(url_for('admin.index'))
                    else: return redirect(url_for('admined'))
                else: flash('该帐号未审核通过！','warning')
            else: flash('密码有误！','warning')
        else: flash('用户名有误！','warning')
    if form.errors: flash(form.errors,'danger')
    return render_template("admin.html", form=form)


# 管理主页
class AdminedForm(FlaskForm):
    field = SelectField('字段名称：',
        render_kw = {"style":"background:#EEFFBB"},
        choices = [('1', '1-用户姓名'), ('2', '2-审核状态')])
    keyword = StringField('字段内容', 
        validators = [DataRequired()], 
        render_kw = {'placeholder': u'输入要搜索的关键词'})
    search = SubmitField('搜索')
    delete = SubmitField('删除')

@app.route('/admined', methods=['GET','POST'])
@login_required
def admined():
    if current_user.isadmin == False: 
        flash('请先登录','danger')
        return redirect(url_for('admin'))  
    
    #分页显示
    page = request.args.get('page', 1, type = int)
    # 每页显示10条
    pagination = User.query.filter_by(isadmin = \
        False).paginate(page = page, per_page = 10)
    users = pagination.items
    
    if not users: 
        flash('没有用户注册','warning')
        return redirect(url_for('regist1'))

    # 获取现在时间，用于计算用户年龄
    now = datetime.now()

    form = AdminedForm()
    if form.validate_on_submit(): 
        field = form.field.data 
        keyword = form.keyword.data 
       
        if field == '1': #姓名
            users1 = User.query.filter_by(name = keyword, \
                isadmin = False).all()
        else: #审核状态
            # python化
            if keyword in '未审核': val = None
            elif keyword in '通过': val = True
            elif keyword in '不通过': val = False
            else:
                flash(f'{keyword}不存在','info')
                return redirect(url_for('admined'))
            users1 = User.query.filter_by(verify = val, isadmin = False).all()
       
        if users1: #若用户存在
            if form.search.data:  
                return render_template("admined.html", #render不刷新页面
                    form = form,
                    users = users1,  
                    nosearch = False, 
                    now = now)
            elif form.delete.data:  
                for user in users1:
                    db.session.delete(user) 
                    db.session.commit() 
                    path = f'static/image/{user.image}'
                    if os.path.exists(path): os.remove(path)  
                flash(f'{keyword}删除成功','success')
                return redirect(url_for('admined')) #redirect刷新页面
        else:
            flash(f'{keyword}不存在','info')
            return redirect(url_for('admined'))
    if form.errors: flash(form.errors,'danger')
    return render_template("admined.html", 
        pagination = pagination, 
        form = form, 
        users = users, 
        nosearch = True,  
        now = now) 

# edit

class EditForm(FlaskForm):
    email = StringField('邮箱：', 
        validators = [
            DataRequired(),
            Email('邮箱格式错误')], 
        render_kw = {'placeholder': u'输入邮箱地址（登录用）'})
    
    password = PasswordField('密码：', 
        validators = [
            DataRequired(), 
            Length(6, 18)], 
        render_kw={'placeholder': u'输入密码（6-18位）'})
    
    confirm = PasswordField('确认密码：', 
        validators = [
            DataRequired(), 
            Length(6, 18), 
            EqualTo('password', "密码不一致")], 
        render_kw = {'placeholder': u'再次输入密码（两次密码必须一致）'})
    
    name = StringField('姓名：', 
        validators = [DataRequired()], 
        render_kw = {'placeholder': u'请输入姓名'})
    
    gender = RadioField('性别：', 
        choices = [
            (1,'男'),
            (0,'女')], 
        validators = [DataRequired()])
    
    birthday = DateField('出生日期：')

    education = SelectField('文化程度：',
        choices = 
            [
                ('1', '1-专科'),
                ('2', '2-本科'), 
                ('3', '3-硕士'), 
                ('4', '4-博士')
            ], 
        validators = [DataRequired()])
    
    image = FileField('照片：',
        validators = [FileAllowed(['jpeg', 'jpg', 'png', 'gif'],'只能上传图片')],
        render_kw = {"style" : "width:100%; border: 1px solid; \
            border-color:silver; padding:4px;border-radius:4px;"})

    
    hobby = SelectMultipleField('爱好：', 
        choices = 
            (
                ('1', '1-篮球'), 
                ('2', '2-足球'), 
                ('3', '3-健身'), 
                ('4', '4-其他')
            ))

    skill = TextAreaField('特长：', 
        validators = [DataRequired()], 
        render_kw={'placeholder': u'请输入个人特长', 'rows': 3}) #3行  

    submit = SubmitField('修改', 
            render_kw = {"style" : "background:#28a745; color : white;"}) # 提交 

@app.route('/edit/<int:userid>', methods=['GET','POST'])
@login_required
def edit(userid):
    if current_user.isadmin == False:
        flash('请先登录','danger')
        return redirect(url_for('admin'))   
    
    form = EditForm()

    user = User.query.get(userid)
    if not user:
        flash('没有用户信息','danger')
        return redirect(url_for('admin')) 

    if request.method == 'POST': 
        name = form.name.data
        
        # python化
        # if form.gender.data == '1':
        #     gender = True
        # else:
        #     gender = False
        gender = True if form.gender.data == '1' else False
        # 

        birthday = form.birthday.data
        education = form.education.data

        if form.image.data: #若已选照片
            image = user.image.split('.')[0] + '.' + form.image.data.filename.split('.')[1]
            form.image.data.save('static/image/' + image) 
        else: image = user.image
            
        email = form.email.data
        hobby = ''.join(form.hobby.data)
        skill = form.skill.data

        #写入用户信息
        User.query.filter_by(id=userid).update({
            'name': name,
            'gender' : gender, 
            'birthday' : birthday, 
            'education' : education, 
            'image' : image, 
            'email' : email, 
            'hobby' : hobby, 
            'skill' : skill})
        db.session.commit()

        flash('保存成功','success')
        return redirect(url_for('admined'))
        
    form.name.data=user.name
    
    # python化
    # if user.gender == True:
    #     form.gender.data = '1'
    # else:
    #     form.gender.data = '0'
    form.gender.data = '1' if user.gender == True else '0'
    # 

    form.birthday.data = user.birthday
    form.education.data = user.education
    form.email.data = user.email
    form.hobby.data = user.hobby
    form.skill.data = user.skill
    return render_template('edit.html', 
        image = '/static/image/' + user.image, 
        form = form)


#【审核通过】
@app.route('/verify_true/<int:userid>') #没有post，网页访问不了
@login_required
def verify_true(userid):
    user = User.query.get(userid)
    if not user:
        flash('没有用户信息','danger')
        return redirect(url_for('admin')) 
    user.verify = True
    db.session.commit()
    return redirect(url_for('admined'))

#【审核不通过】
@app.route('/verify_false/<int:userid>')
@login_required
def verify_false(userid):
    user = User.query.get(userid)
    if not user:
        flash('没有用户信息','danger')
        return redirect(url_for('admin')) 
    user.verify = False
    db.session.commit()
    return redirect(url_for('admined'))

#【删除用户】
@app.route('/delete/<int:userid>', methods=['POST'])
@login_required
def delete(userid):
    if current_user.isadmin == False:
        flash('请先登录','danger')
        return redirect(url_for('admin'))   

    user = User.query.get(userid)
    if not user:
        flash('没有用户信息','danger')
        return redirect(url_for('admin')) 

    db.session.delete(user)
    db.session.commit()
    path = 'static/image/' + user.image
    # python化
    if os.path.exists(path): os.remove(path)  
    flash(f'{user.name}删除成功','success')
    return redirect(url_for('admined'))


# 密码初始化

class InitpwdForm(FlaskForm):
    email = StringField('邮箱：', 
        validators = [DataRequired(),Email('邮箱格式错误')], 
        render_kw = {'placeholder': u'输入邮箱地址（登录用）'})
    submit = SubmitField('密码初始化',
        render_kw = {"style" : "background:#ffc107; color : white;"})


@app.route('/initpwd',methods=['GET','POST'])
@login_required
def initpwd():
    if current_user.isadmin == False:
        flash('请先登录','danger')
        return redirect(url_for('admin'))   
    
    form = InitpwdForm()
    if form.validate_on_submit():
        email = form.email.data
        user = User.query.filter_by(email = email).first() 
        if user:
            password = generate_password_hash('123456')
            user.password = password
            db.session.commit()
            flash(email + '的初始密码为：123456','success')
        else: flash(email + '不存在！','warning')
    if form.errors: flash(form.errors,'danger')
    return render_template("initpwd.html", form=form)



#【系统初始化】

class InitsysForm(FlaskForm):
    deldir = BooleanField('删除用户文件')
    initdb = BooleanField('数据库初始化')
    submit = SubmitField('系统初始化',
        render_kw = {"style" : "background:red; color : white;"})

#【删除文件】
def delfile(path):
    for i in os.listdir(path) : 
        file = path + "/" + i 
        # python化
        if os.path.isfile(file) == True: os.remove(file)

#【初始化视图】
@app.route('/initsys', methods=['GET', 'POST'])
@login_required
def initsys():
    if current_user.isadmin == False:
        flash('请先登录','danger')
        return redirect(url_for('admin'))   
    
    form = InitsysForm()
    if form.validate_on_submit():
        deldir = form.deldir.data
        initdb = form.initdb.data
        message = ''
        if deldir: 
            message = '删除用户文件'
            path = 'static/image'
            delfile(path)
            path = 'static/file'
            delfile(path)
        if initdb: 
            message += '数据库初始化'
            db.session.query(User).filter_by(isadmin = False).delete()
            db.session.commit()

        flash(message + '完毕！','success')
        return redirect(url_for('admin'))
    if form.errors: flash(form.errors,'danger')
    return render_template("initsys.html", form=form)

# 相册

@app.route('/album')
@login_required
def album():
    if current_user.isadmin == False:
        flash('请先登录','danger')
        return redirect(url_for('admin'))   

    page = request.args.get('page', 1, type = int)
    # 每页显示20条
    pagination = User.query.filter_by(isadmin = False).paginate(page = page, per_page = 20)
    users = pagination.items
    
    #每行显示5张照片
    imgrow = []; imgcol = []; iend = 0
    for i,u in enumerate(users,1):
        imgrow.append(u.image)
        if i % 5 == 0:
            imgcol.append(imgrow)
            imgrow = []
            iend = i

    #最后不够5的照片处理
    # python化
    # imgrow = []
    # for u in users[iend:]: imgrow.append(u.image)
    imgrow = [u.image for u in users[iend:]]
    # 
    imgcol.append(imgrow)       
    
    return render_template("album.html", pagination = pagination,users = users,imgcol = imgcol)


#【超级用户视图】
# @app.route('/super')
# def super():
#     username = session.get('super')
#     sup = app.config['SUPER_NAME']
#     if username != sup: #若用户不是super
#         flash('请先登录','danger')
#         return redirect(url_for('admin'))

#     page = request.args.get('page', 1, type = int)
#     pagination = \
#     User.query.order_by(User.isadmin.desc()).paginate(page, per_page = 10)
#     users = pagination.items
#     return render_template("super.html", users = users, pagination = pagination)

@app.route('/super/<int:userid>')
def super(userid):
    username = session.get('super')
    sup = app.config['SUPER_NAME']
    if username != sup: #若用户不是super
        flash('请先登录','danger')
        return redirect(url_for('admin'))

    user = User.query.get(userid)
    if user:
        users = User.query.order_by(User.isadmin.desc()).all() 
        return render_template('super.html', 
            user = user, #用于brows/userid
            users = users, 
            image = '/static/image/' + user.image)
    else:
        flash('没有报名信息','danger')
        return redirect(url_for('admin')) 



@app.route('/setadmin/<int:userid>') 
def setadmin(userid):
    user = User.query.get(userid)
    if not user:
        flash('没有用户信息','danger')
        return redirect(url_for('admin')) 
    # python化
    user.verify = True; user.isadmin = True
    db.session.commit()
    # return redirect(url_for('super'))
    return redirect(url_for('super', userid = user.id))

@app.route('/notadmin/<int:userid>')
def notadmin(userid):
    user = User.query.get(userid)
    if not user:
        flash('没有用户信息','danger')
        return redirect(url_for('admin')) 
    # python化
    user.verify = False; user.isadmin = False
    db.session.commit()
    # return redirect(url_for('super'))
    return redirect(url_for('super', userid = user.id))



# 可视化
from pyecharts.charts import Pie
import pyecharts.options as opts

#【饼状图视图】
@app.route('/pie')
def pie():
    #获取用户信息
    users = User.query.filter_by(isadmin = False).all()
    if not users: #若一个用户都没注册
        flash('没有用户注册','info')
        return redirect(url_for('regist1'))

    #Pie
    male = User.query.filter_by(gender = True, isadmin = False).count()
    fmale = User.query.filter_by(gender = False, isadmin = False).count()
    sex = ["女", "男"]
    ratio = [fmale, male]
    data_pair = [list(data) for data in zip(sex, ratio)]
    
    pie = Pie()
    pie.add(series_name="人数",data_pair=data_pair, rosetype='radius')
    pie.set_global_opts(title_opts=opts.TitleOpts(title="【男女人数】"))  
    return render_template('echart.html', 
        title = '饼图',
        data = pie.dump_options())

#【极坐标系视图】
from pyecharts.charts import Polar

@app.route('/polar')
def polar():
    users = User.query.filter_by(isadmin = False).all()
    if not users: 
        flash('没有用户注册','info')
        return redirect(url_for('regist1'))

    # 男生
    edu1m = User.query.filter_by(education = '1', gender = True, isadmin = False).count()
    edu2m = User.query.filter_by(education = '2', gender = True, isadmin = False).count()
    edu3m = User.query.filter_by(education = '3', gender = True, isadmin = False).count()
    edu4m = User.query.filter_by(education = '4', gender = True, isadmin = False).count()
    # 女生
    edu1f = User.query.filter_by(education = '1', gender = False, isadmin = False).count()
    edu2f = User.query.filter_by(education = '2', gender = False, isadmin = False).count()
    edu3f = User.query.filter_by(education = '3', gender = False, isadmin = False).count()
    edu4f = User.query.filter_by(education = '4', gender = False, isadmin = False).count()

    x = ["高职", "本科", "硕士", "博士"]
    y1 = [edu1f, edu2f, edu3f, edu4f]
    y2 = [edu1m, edu2m, edu3m, edu4m]
    pol = Polar()

    pol.add_schema(angleaxis_opts=opts.AngleAxisOpts(data=x))
    pol.add("女性", y1, type_="bar", stack="stack0")
    pol.add("男性", y2, type_="bar", stack="stack0")
    pol.set_global_opts(title_opts=opts.TitleOpts(title="【文化程度/性别】"))

    return render_template('echart.html', 
        title = '极坐标系',
        data = pol.dump_options())


#【柱状图视图】

def fillzero(m,m1,m2,m3,m4):
    x = []; y1 = []; y2 = []; y3 = []; y4 = [] 
    msort=sorted(m,key=(lambda x:x[0]))
    for ms in msort:
        x.append(str(ms[0])+'年代')
        # python化
        # found1 = False
        # found2 = False
        # found3 = False
        # found4 = False   
        found1,found2,found3,found4 = False,False,False,False 
        # 
        # python化
        for j in m1:
            if ms[0] == j[0]: found1 = True; ms1 = j[1]; break
        for j in m2:
            if ms[0] == j[0]: found2 = True; ms2 = j[1]; break
        for j in m3:
            if ms[0] == j[0]: found3 = True; ms3 = j[1]; break
        for j in m4:
            if ms[0] == j[0]: found4 = True; ms4 = j[1]; break
        
        # 

        # python化
        # if found1: y1.append(ms1)
        # else: y1.append(0)
        # if found2: y2.append(ms2)
        # else: y2.append(0)
        # if found3: y3.append(ms3)
        # else: y3.append(0)
        # if found4: y4.append(ms4)
        # else: y4.append(0)
        y1.append(ms1) if found1 else y1.append(0)
        y2.append(ms2) if found2 else y2.append(0)
        y3.append(ms3) if found3 else y3.append(0)
        y4.append(ms4) if found4 else y4.append(0)
        # 
    return x,y1,y2,y3,y4

from pyecharts.charts import Bar
from collections import Counter
@app.route('/bar')
def bar():
    #获取用户信息
    users = User.query.filter_by(isadmin = False).all()
    if not users: 
        flash('没有用户注册','info')
        return redirect(url_for('regist1'))

    #年代：从出生年月的年份中取前3位，第四位换成0
    years = [] #年代 
    y1 = []; y2 = []; y3 = []; y4 = [] 

    for user in users:
        year = str(user.birthday.year)
        years.append(f'{year[:3]}0')

        # python化
        if '1' in user.hobby: y1.append(f'{year[:3]}0')
        if '2' in user.hobby: y2.append(f'{year[:3]}0')
        if '3' in user.hobby: y3.append(f'{year[:3]}0')
        if '4' in user.hobby: y4.append(f'{year[:3]}0')
        
    # 统计各年代人数
    m = Counter(years).most_common()
    m1 = Counter(y1).most_common()
    m2 = Counter(y2).most_common()
    m3 = Counter(y3).most_common()
    m4 = Counter(y4).most_common()

    x,y1,y2,y3,y4 = fillzero(m,m1,m2,m3,m4)

    bar = Bar()
    bar.add_xaxis(x)

    bar.add_yaxis("篮球", y1)
    bar.add_yaxis("足球", y2)
    bar.add_yaxis("健身", y3)
    bar.add_yaxis("其他", y4)

    bar.set_global_opts(title_opts=opts.TitleOpts(title="【年代/爱好】"))  
    return render_template('echart.html', 
        title = '柱状图',
        data = bar.dump_options())


#【线状图视图】

from pyecharts.charts import Line

@app.route('/line')
def line():
    users = User.query.filter_by(isadmin = False).all()

    if not users: 
        flash('没有用户注册','info')
        return redirect(url_for('regist1'))

    
    #Line
    #年代：从出生年月的年份中取前3位，第四位换成0
    years = [] #不分性别 
    y1 = []; y2 = [] #女性
    for user in users:
        year = str(user.birthday.year)
        years.append(f'{year[:3]}0')
        # python化
        # if user.gender:
        #     y1.append(f'{year[:3]}0')
        # else:
        #     y2.append(f'{year[:3]}0')
        y1.append(f'{year[:3]}0') if user.gender \
            else y2.append(f'{year[:3]}0')
        # 

    # 统计各年代人数
    most = Counter(years).most_common()
    m1 = Counter(y1).most_common()
    m2 = Counter(y2).most_common()
    
    # 按年代前后排序
    msort=sorted(most,key=(lambda x:x[0]))
    ms1=sorted(m1,key=(lambda x:x[0]))
    ms2=sorted(m2,key=(lambda x:x[0]))
    
    # python化
    # x = [] #年代 
    # y = [] #人数
    # for ms in msort:
    #     x.append(str(ms[0])+'年代')
    #     y.append(ms[1])

    # y1 = [] #男人数
    # for ms in ms1: y1.append(ms[1])
    # y2 = [] #女人数
    # for ms in ms2: y2.append(ms[1])
    x =[str(ms[0])+'年代' for ms in msort]
    y =[ms[1] for ms in msort]

    y1 =[ms[1] for ms in ms1]
    y2 =[ms[1] for ms in ms2]
    # 

    line = Line()
    line.add_xaxis(x) #X轴 年代
    line.add_yaxis("女性", y2) #Y轴 女人数
    line.add_yaxis("男性", y1) #Y轴 男人数
    line.add_yaxis("合计", y, is_smooth=True) #Y轴 人数
    line.set_global_opts(title_opts=opts.TitleOpts(title="【年代/性别】")) 

    return render_template('echart.html', 
        title = '线状图',
        data = line.dump_options())


#【散状图视图】
from pyecharts.charts import Scatter


@app.route('/scatter')
def scatter():
    users = User.query.filter_by(isadmin = False).all()
    if not users: 
        flash('没有用户注册','info')
        return redirect(url_for('regist1'))

    #年代：从出生年月的年份中取前3位，第四位换成0
    # python化
    # years = []
    # for user in users:
    #     year = str(user.birthday.year)
    #     years.append(f'{year[:3]}0')
    years = [f'{str(user.birthday.year)[:3]}0' for user in users]
    # 

    # 统计各年代人数
    most = Counter(years).most_common()
    
    # 按年代前后排序
    msort=sorted(most,key=(lambda x:x[0]))
    
    # python化
    # x = [] #年代 
    # y = [] #人数
    # for ms in msort:
    #     x.append(str(ms[0])+'年代')
    #     y.append(ms[1])
    x =[str(ms[0])+'年代' for ms in msort]
    y =[ms[1] for ms in msort]
    # 

    sca = Scatter()
    sca.add_xaxis(x)
    sca.add_yaxis("人数", y)
    sca.set_global_opts(
        title_opts=opts.TitleOpts(title="【年代/人数】"),visualmap_opts = \
        opts.VisualMapOpts(
            type_="size",  #映射大小
            max_=max(y),
            min_=min(y)
            ),
    )


    return render_template('echart.html', 
        title = '散状图',
        data = sca.dump_options())


#【雷达图视图】
from pyecharts.charts import Radar
@app.route('/radar')
def radar():
    users = User.query.filter_by(isadmin = False).all()

    if not users: 
        flash('没有用户注册','info')
        return redirect(url_for('regist1'))

    # python化
    # first_name = []
    # for user in users:
    #     first_name.append(user.name[0])
    first_name = [user.name[0] for user in users]
    # 

    most = Counter(first_name).most_common()
    
    # python化
    # data = []
    # schema = []
    # for m in most:
    #     data.append(m[1])
    #     schema.append(opts.RadarIndicatorItem(name=m[0],max_=most[0][1]))
    schema =[opts.RadarIndicatorItem(name=m[0],
        max_=most[0][1]) for m in most]
    data =[m[1] for m in most]
    # 

    data = [data]
    radar = Radar()
    radar.add_schema(schema)

    radar.add('姓氏',data)
    radar.set_global_opts(title_opts=opts.TitleOpts('【姓氏/人数】'))
    return render_template('echart.html', title = '雷达图',data = radar.dump_options())



from pyecharts.charts import Kline

#【K线图视图】
@app.route('/kline')
def kline():
    #获取用户信息
    users = User.query.filter_by(isadmin = False).all()
    if not users: 
        flash('没有用户注册','info')
        return redirect(url_for('regist1'))

    #年代：从出生年月的年份中取前3位，第四位换成0
    # python化
    years = []; y1 = []; y2 = []; y3 = []; y4 = []
    for user in users:
        year = str(user.birthday.year)
        years.append(f'{year[:3]}0')
        # python化
        if user.education == '1': y1.append(f'{year[:3]}0')
        elif user.education == '2': y2.append(f'{year[:3]}0')
        elif user.education == '3': y3.append(f'{year[:3]}0')
        elif user.education == '4': y4.append(f'{year[:3]}0')

    
    # 统计各年代人数
    m = Counter(years).most_common()
    m1 = Counter(y1).most_common()
    m2 = Counter(y2).most_common()
    m3 = Counter(y3).most_common()
    m4 = Counter(y4).most_common()

    x,y1,y2,y3,y4 = fillzero(m,m1,m2,m3,m4)
    
    # open：本科
    # close：硕士
    # low：高职
    # high：博士
    # 虽然这样组合看起来不合理，但是学习怎么用就可以
    y = [list(z) for z in zip(y2, y3, y1, y4)]

    kln = Kline()
    kln.add_xaxis(x)
    kln.add_yaxis('',y)
    kln.set_global_opts(title_opts = opts.TitleOpts(title='【年代/文化程度】', 
        subtitle="open：基本（本科）；close：够格（硕士）；lowest：最低（高职）；highest：最高（博士）"),
    )

    return render_template('echart.html', 
        title = 'K线图',
        data = kln.dump_options())

#【箱形图视图】
from pyecharts.charts import Boxplot
@app.route('/boxplot')
def boxplot():
    users = User.query.filter_by(isadmin = False).all()
    if not users: 
        flash('没有用户注册','info')
        return redirect(url_for('regist1'))

    years = []; y1 = []; y2 = []; y3 = []; y4 = [] #其他
    for user in users:
        year = str(user.birthday.year)
        years.append(year)
        
        if '1' in user.hobby: y1.append(year)
        if '2' in user.hobby: y2.append(year)
        if '3' in user.hobby: y3.append(year)
        if '4' in user.hobby: y4.append(year)
    
    m = Counter(years).most_common()
    m1 = Counter(y1).most_common()
    m2 = Counter(y2).most_common()
    m3 = Counter(y3).most_common()
    m4 = Counter(y4).most_common()

    x,y1,y2,y3,y4 = fillzero(m,m1,m2,m3,m4)

    x = ['篮球','足球','健身','其他']
    box = Boxplot()
    box.add_xaxis(x)
    box.add_yaxis('',box.prepare_data([y1,y2,y3,y4]))
    box.set_global_opts(title_opts=opts.TitleOpts(title='【爱好/人数】'))

    return render_template('echart.html', 
        title = '箱形图',
        data = box.dump_options())


#【漏斗图视图】
from pyecharts.charts import Funnel
@app.route('/funnel')
def funnel():
    users = User.query.filter_by(isadmin = False).all()
    if not users: 
        flash('没有用户注册','info')
        return redirect(url_for('regist1'))

    # python化
    # sklstr = '' #全部用户特长
    # sklist = [] #
    # name = []
    # for user in users:
    #     sklstr += user.skill
    #     sklist.append(user.skill)
    #     name.append(user.name)
    sklist = [user.skill for user in users]
    name = [user.name for user in users]
    sklstr = ''.join(sklist)
    # 
    # 统计全部用户特长词频
    words = jieba.cut(sklstr, cut_all = False)
    count = Counter(words) #全部用户特长
    
    #统计每个句子权重
    sklval = [] #安循序存放句子的权重
    for s in sklist:
        sval = 0
        wds = jieba.cut(s, cut_all=False)
        
        for w in wds:
            for key, val in count.items():
                if w == key: sval += val 

        sklval.append(sval)


    arr = np.array(sklval)
    top = np.argsort(-arr) #返回从小到大排序后的索引值
    
    # python化
    # ntop = []
    # vtop = []
    # for i in top[0:10]:
    #     ntop.append(name[i])
    #     vtop.append(sklval[i])
    ntop = [name[i] for i in top[0:10]]
    vtop = [sklval[i] for i in top[0:10]]
    # 

    data=[(i,j) for i ,j in zip(ntop,vtop)]
    
    fun = Funnel()
    fun.add('',data,label_opts=opts.LabelOpts(position="inside"))
    fun.set_global_opts(title_opts=opts.TitleOpts(title='【特长权重】'))
    
    return render_template('echart.html', title = '漏斗图', data = fun.dump_options())

# 词云图：因为静态WordCloud冲突，所以不能同时用
from pyecharts.charts import WordCloud as echartsWordCloud
@app.route('/wordcloud')
def wordcloud():
    users = User.query.filter_by(isadmin = False).all()
    if not users: 
        flash('没有用户注册','info')
        return redirect(url_for('regist1'))
    
    skills = ''
    for user in users: skills += user.skill

    words = jieba.cut(skills, cut_all = False)
    cwords = []
    for word in words:
        pin = ''.join(lazy_pinyin(word, style = Style.TONE))
        if pin != word and len(word) >= 2: cwords.append(word)

    wc = echartsWordCloud()
    data = Counter(cwords).most_common()
    wc.add('', data, shape='circle')
    # wc.set_global_opts(title_opts=opts.TitleOpts('词汇频率'))
        
    return render_template('echart.html', title = '字符云',data = wc.dump_options())



# 「Flask-Admin」

# 主页视图
from flask_admin import AdminIndexView
class HomeView(AdminIndexView):
    def is_accessible(self):
        return current_user.is_authenticated and \
            current_user.isadmin

    def inaccessible_callback(self, name, **kwargs):
        # 如果用户不能登录，转到登录页面
        return redirect(url_for('admin', next=request.url))


from flask_admin import Admin
from flask_babelex import Babel 

babel = Babel(app)
app.config['BABEL_DEFAULT_LOCALE'] = 'zh_CN' # 中文
app.config['FLASK_ADMIN_SWATCH'] = 'cerulean' # 外观

admin = Admin(app, 
    index_view = HomeView(name='退出'), 
    name="Flask-Admin后台", 
    template_mode='bootstrap3')


# 「用户标管理」
from flask_admin.contrib.sqla import ModelView

class UserView(ModelView):
    def is_accessible(self):
        return current_user.is_authenticated and current_user.isadmin

    def inaccessible_callback(self, name, **kwargs):
        # redirect to login page if user doesn't have access
        return redirect(url_for('admin', next=request.url))

    column_labels = {
        'email' : '邮箱',
        'password':'密码',
        'name':'姓名',
        'gender':'性别',
        'birthday':'出生日期',
        'isadmin':'管理员',
        'verify':'状态',
        'education':'文化程度',
        'image':'照片',
        'hobby':'爱好',
        'skill':'特长',
    }

    can_create = False
    can_view_details = True
    can_export = True
    page_size = 15
    # 主页不显示的字段
    column_exclude_list = ['email','password','birthday','image','education',
        'skill','hobby']
    #不能编辑的字段
    form_excluded_columns = ['email','password','image', 'hobby'] 
    form_choices = {
        'education':[
            ('1', '1-高职'),
            ('2', '2-本科'), 
            ('3', '3-硕士'), 
            ('4', '4-博士')
        ]
    }

# 主菜单
admin.add_view(UserView(User, db.session, name='用户表管理'))

# 「系统初始化」
from flask_admin import BaseView, expose

class InitsysView(BaseView):
    @expose('/', methods=('GET', 'POST'))
    @login_required
    def index(self):
        form = InitsysForm()
        if form.validate_on_submit():
            deldir = form.deldir.data
            initdb = form.initdb.data
            message = ''
            if deldir: 
                message = '删除用户文件'
                path = 'static/image'
                delfile(path)
                path = 'static/file'
                delfile(path)
            if initdb: 
                message += '数据库初始化'
                db.session.query(User).filter_by(isadmin = False).delete()
                db.session.commit()

            flash(message + '完毕！','success')
            return redirect(url_for('admin'))
        if form.errors: flash(form.errors,'danger')

        return self.render('admin/initsys.html',form = form)


admin.add_view(InitsysView(name='系统初始化'))

class AdminedView(BaseView):
    @expose('/', methods=('GET', 'POST'))
    @login_required
    def index(self):
        # users=User.query.filter_by(isadmin = False).all()

        #分页显示
        page = request.args.get('page', 1, type = int)
        # 每页显示10条
        pagination = User.query.filter_by(isadmin = False).paginate(page = page, per_page = 10)
        users = pagination.items
        
        if not users: 
            flash('没有用户注册','warning')
            return redirect(url_for('regist1'))

        # 获取现在时间，用于计算用户年龄
        now = datetime.now()

        return self.render("admin/admined.html", 
            pagination = pagination,
            users = users, 
            now = now) 

#【审核通过】
@app.route('/AdminedView_verify_true/<int:userid>') #没有post，网页访问不了
@login_required
def AdminedView_verify_true(userid):
    user = User.query.get(userid)
    if not user:
        flash('没有用户信息','danger')
        return redirect(url_for('admin')) 
    user.verify = True
    db.session.commit()
    return redirect(url_for('adminedview.index'))

#【审核不通过】
@app.route('/AdminedView_verify_false/<int:userid>')
@login_required
def AdminedView_verify_false(userid):
    user = User.query.get(userid)
    if not user:
        flash('没有用户信息','danger')
        return redirect(url_for('admin')) 
    user.verify = False
    db.session.commit()
    return redirect(url_for('adminedview.index'))

#【删除用户】
@app.route('/AdminedView_delete/<int:userid>', methods=['POST'])
@login_required
def AdminedView_delete(userid):
    user = User.query.get(userid)
    if not user:
        flash('没有用户信息','danger')
        return redirect(url_for('admin')) 

    db.session.delete(user)
    db.session.commit()
    path = 'static/image/' + user.image
    # python化
    if os.path.exists(path): os.remove(path)  
    flash(f'{user.name}删除成功','success')
    return redirect(url_for('adminedview.index'))

admin.add_view(AdminedView(name='管理员页面'))

class InitpwdView(BaseView):
    @expose('/', methods=('GET', 'POST'))
    @login_required
    def index(self):
        form = InitpwdForm()
        if form.validate_on_submit():
            email = form.email.data
            user = User.query.filter_by(email = email).first() 
            if user:
                password = generate_password_hash('123456')
                user.password = password
                db.session.commit()
                flash(email + '的初始密码为：123456','success')
            else: flash(email + '不存在！','warning')
        if form.errors: flash(form.errors,'danger')

        return self.render('admin/initpwd.html',form = form)


admin.add_view(InitpwdView(name='密码初始化'))

class AlbumView(BaseView):
    @expose('/', methods=('GET', 'POST'))
    @login_required
    def index(self):
        page = request.args.get('page', 1, type = int)
        # 每页显示15条
        pagination = User.query.filter_by(isadmin = False).paginate(page = page, per_page = 15)
        users = pagination.items
        
        #每行显示5张照片
        imgrow = []; imgcol = []; iend = 0
        for i,u in enumerate(users,1):
            imgrow.append(u.image)
            if i % 5 == 0:
                imgcol.append(imgrow)
                imgrow = []
                iend = i

        #最后不够5的照片处理
        # python化
        # imgrow = []
        # for u in users[iend:]: imgrow.append(u.image)
        imgrow = [u.image for u in users[iend:]]
        # 
        imgcol.append(imgrow)       
        
        return self.render("admin/album.html", pagination = pagination,users = users,imgcol = imgcol)


admin.add_view(AlbumView(name='用户图相册'))




























# ==========================================================================================================================================test
import numpy as np
from dateutil import parser
from shutil import copyfile
@app.route('/test')
def test():
    # 用于产生姓，源自网络    
    first_name  = ["赵", "钱", "孙", "李", "周", "吴", "郑", 
        "王", "冯", "陈", "褚", "卫", "蒋", "沈", "韩", "杨", 
        "朱", "秦", "尤", "许", "何", "吕", "施", "张", "孔", 
        "曹", "严", "华", "金", "魏", "陶", "姜", "戚", "谢", 
        "邹", "喻", "柏", "水", "窦", "章", "云", "苏", "潘", 
        "葛", "奚", "范", "彭", "郎", "鲁", "韦", "昌", "马", 
        "苗", "凤", "花", "方"]
    # 用于产生名，源自网络
    second_name = ["静", "霞", "雪", "思", "平", "东", "志宏", 
        "峰", "磊", "雷", "文","明浩", "光", "超", "军", "达", 
        "伟", "华", "建国", "洋", "刚", "万里", "爱民", "牧", 
        "陆", "路", "昕", "鑫", "兵", "硕"]
    

    # 生成密码，hash加密
    password = generate_password_hash('123456')

    # 用于特长
    # python化
    # with open("test/wisdom.txt") as file: 
        # wisdom = file.readlines()
    wisdom = [line for line in open("test/wisdom.txt")]
    # file.close()

    # 填零位数，最多可生成4位数用户
    # zero = 4
    # 进入0~99个循环
    for iii in range(1,100):
        # 随机生成0~9组成的9位数字
        n9 = np.random.randint(0,9,size=9)
        # 转化字符串
        n9str =''.join([str(n) for n in n9])
        email = f'13{n9str}@163.com'

        #随机生成姓名
        name = random.choice(first_name) + random.choice(second_name)
        # 生成目标照片文件名
        now = datetime.now() #当前时间
        strnow = now.strftime("%Y%m%d%H%M%s") + str(iii)
        image = strnow + '.png'
        # 随机生成1和2
        rand = random.randint(1, 2)
        if rand == 1: #男性
            gender = True
            copyfile('test/man.png', 'static/image/' + image)
        else: #女性
            gender = False
            copyfile('test/woman.png', 'static/image/' + image)
        
        # 随机生成1~4间的整数（含1和4）
        education = random.randint(1, 4)
        # 随机生成年龄
        # python化
        if education == 1: age = random.randint(15, 30)
        elif education == 2: age = random.randint(18, 40)
        elif education == 3: age = random.randint(22, 50)
        else: age = random.randint(25, 60)
        
        # 生成出生日期
        y = datetime.now().year - age
        m = random.randint(1, 12) 
        d = random.randint(1, 28)
        birthday = parser.parse(f'{str(y)}-{str(m).zfill(2)}-{str(d).zfill(2)}')
       
        # 爱好 
        # 随机产生包含1~4的4位数字
        n4 = np.random.randint(1,5,size=4)
        # 转换成字符串后删掉重复的数字
        hobby = ''
        for i in str(n4):
            if i not in hobby and i in '1234': hobby += i

        skill = wisdom[iii]
        
        
        # 用户注册信息写入User表
        user=User( 
            email = email, 
            isadmin = False,
            verify = None,
            password = password, 
            name = name,
            gender = gender, 
            birthday = birthday, 
            education = education, 
            image = image,
            hobby = hobby, 
            skill = skill)
        db.session.add(user)
        db.session.commit()

    return '<h1>数据生成完毕！</h1>'
# =========================================================================================================================================test
# python化
if __name__ == "__main__": app.run(debug = True)
